{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5c0122e65c053f38",
   "metadata": {},
   "source": [
    "# Hosting Strands Agents with OpenAI models in Amazon Bedrock AgentCore Runtime\n",
    "\n",
    "## Overview\n",
    "\n",
    "In this tutorial we will learn how to host your existing agent, using Amazon Bedrock AgentCore Runtime. \n",
    "\n",
    "We will focus on a Strands Agents with OpenAI model example. For Strands Agents with Amazon Bedrock model check [here](../01-strands-with-bedrock-model) and \n",
    "for LangGraph with Amazon Bedrock model check [here](../02-langgraph-with-bedrock-model)\n",
    "\n",
    "\n",
    "### Tutorial Details\n",
    "\n",
    "| Information         | Details                                                                  |\n",
    "|:--------------------|:-------------------------------------------------------------------------|\n",
    "| Tutorial type       | Conversational                                                           |\n",
    "| Agent type          | Single                                                                   |\n",
    "| Agentic Framework   | Strands Agents                                                           |\n",
    "| LLM model           | GPT 4.1 mini                                                             |\n",
    "| Tutorial components | Hosting agent on AgentCore Runtime. Using Strands Agent and OpenAI Model |\n",
    "| Tutorial vertical   | Cross-vertical                                                           |\n",
    "| Example complexity  | Easy                                                                     |\n",
    "| SDK used            | Amazon BedrockAgentCore Python SDK and boto3                             |\n",
    "\n",
    "### Tutorial Architecture\n",
    "\n",
    "In this tutorial we will describe how to deploy an existing agent to AgentCore runtime. \n",
    "\n",
    "For demonstration purposes, we will  use a Strands Agent using Amazon Bedrock models\n",
    "\n",
    "In our example we will use a very simple agent with two tools: `get_weather` and `get_time`. \n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/architecture_runtime.png\" width=\"60%\"/>\n",
    "</div>\n",
    "\n",
    "### Tutorial Key Features\n",
    "\n",
    "* Hosting Agents on Amazon Bedrock AgentCore Runtime\n",
    "* Using OpenAI models\n",
    "* Using Strands Agents\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a676f58ecf52b42",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "\n",
    "To execute this tutorial you will need:\n",
    "* Python 3.10+\n",
    "* AWS credentials\n",
    "* Amazon Bedrock AgentCore SDK\n",
    "* Strands Agents\n",
    "* Docker running"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "initial_id",
   "metadata": {
    "jupyter": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "#!uv add -r requirements.txt --active"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca924a7a2731e26f",
   "metadata": {},
   "source": [
    "## Creating your agents and experimenting locally\n",
    "\n",
    "Before we deploy our agents to AgentCore Runtime, let's develop and run them locally for experimentation purposes.\n",
    "\n",
    "For production agentic applications we will need to decouple the agent creation process from the agent invocation one. With AgentCore Runtime, we will decorate the invocation part of our agent with the `@app.entrypoint` decorator and have it as the entry point for our runtime. Let's first look how each agent is developed during the experimentation phase.\n",
    "\n",
    "The architecture here will look as following:\n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/architecture_local.png\" width=\"60%\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7d386ab54e85e63",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile strands_agents_openai.py\n",
    "from strands import Agent, tool\n",
    "from strands_tools import calculator # Import the calculator tool\n",
    "import argparse\n",
    "import json\n",
    "from strands.models.litellm import LiteLLMModel\n",
    "import os\n",
    "\n",
    "os.environ[\"AZURE_API_KEY\"] = \"<YOUR_API_KEY>\"\n",
    "os.environ[\"AZURE_API_BASE\"] = \"<YOUR_API_BASE>\"\n",
    "os.environ[\"AZURE_API_VERSION\"] = \"<YOUR_API_VERSION>\"\n",
    "\n",
    "# Create a custom tool \n",
    "@tool\n",
    "def weather():\n",
    "    \"\"\" Get weather \"\"\" # Dummy implementation\n",
    "    return \"sunny\"\n",
    "\n",
    "model = \"azure/gpt-4.1-mini\"\n",
    "litellm_model = LiteLLMModel(\n",
    "    model_id=model, params={\"max_tokens\": 32000, \"temperature\": 0.7}\n",
    ")\n",
    "\n",
    "\n",
    "agent = Agent(\n",
    "    model=litellm_model,\n",
    "    tools=[calculator, weather],\n",
    "    system_prompt=\"You're a helpful assistant. You can do simple math calculation, and tell the weather.\"\n",
    ")\n",
    "\n",
    "def strands_agent_open_ai(payload):\n",
    "    \"\"\"\n",
    "    Invoke the agent with a payload\n",
    "    \"\"\"\n",
    "    user_input = payload.get(\"prompt\")\n",
    "    response = agent(user_input)\n",
    "    return response.message['content'][0]['text']\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    parser = argparse.ArgumentParser()\n",
    "    parser.add_argument(\"payload\", type=str)\n",
    "    args = parser.parse_args()\n",
    "    response = strands_agent_open_ai(json.loads(args.payload))\n",
    "    print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68499675-db8d-47c6-8c0c-5d66dcb06229",
   "metadata": {},
   "source": [
    "#### Invoking local agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1226d59e6b56c96",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-29T21:52:06.461281Z",
     "start_time": "2025-06-29T21:52:06.456854Z"
    }
   },
   "outputs": [],
   "source": [
    "!python strands_agents_openai.py '{\"prompt\": \"What is the weather now?\"}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "932110e6-fca6-47b6-b7c5-c4714a866a80",
   "metadata": {},
   "source": [
    "## Preparing your agent for deployment on AgentCore Runtime\n",
    "\n",
    "Let's now deploy our agents to AgentCore Runtime. To do so we need to:\n",
    "* Import the Runtime App with `from bedrock_agentcore.runtime import BedrockAgentCoreApp`\n",
    "* Initialize the App in our code with `app = BedrockAgentCoreApp()`\n",
    "* Decorate the invocation function with the `@app.entrypoint` decorator\n",
    "* Let AgentCoreRuntime control the running of the agent with `app.run()`\n",
    "\n",
    "### Strands Agents with OpenAI model\n",
    "Let's start with our Strands Agent using the GPT 4.1 mini model. All the others will work exactly the same."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b845b32-a03e-45c2-a2f0-2afba8069f47",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile strands_agents_openai.py\n",
    "from strands import Agent, tool\n",
    "from strands_tools import calculator # Import the calculator tool\n",
    "import argparse\n",
    "import json\n",
    "from strands.models.litellm import LiteLLMModel\n",
    "import os\n",
    "from bedrock_agentcore.runtime import BedrockAgentCoreApp\n",
    "\n",
    "app = BedrockAgentCoreApp()\n",
    "\n",
    "os.environ[\"AZURE_API_KEY\"] = \"<YOUR_API_KEY>\"\n",
    "os.environ[\"AZURE_API_BASE\"] = \"<YOUR_API_BASE>\"\n",
    "os.environ[\"AZURE_API_VERSION\"] = \"<YOUR_API_VERSION>\"\n",
    "\n",
    "# Create a custom tool \n",
    "@tool\n",
    "def weather():\n",
    "    \"\"\" Get weather \"\"\" # Dummy implementation\n",
    "    return \"sunny\"\n",
    "\n",
    "model = \"azure/gpt-4.1-mini\"\n",
    "litellm_model = LiteLLMModel(\n",
    "    model_id=model, params={\"max_tokens\": 32000, \"temperature\": 0.7}\n",
    ")\n",
    "\n",
    "\n",
    "agent = Agent(\n",
    "    model=litellm_model,\n",
    "    tools=[calculator, weather],\n",
    "    system_prompt=\"You're a helpful assistant. You can do simple math calculation, and tell the weather.\"\n",
    ")\n",
    "\n",
    "\n",
    "@app.entrypoint\n",
    "def strands_agent_open_ai(payload):\n",
    "    \"\"\"\n",
    "    Invoke the agent with a payload\n",
    "    \"\"\"\n",
    "    user_input = payload.get(\"prompt\")\n",
    "    response = agent(user_input)\n",
    "    return response.message['content'][0]['text']\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c64db7b5-0f1b-475f-9bf2-467b4449d46a",
   "metadata": {},
   "source": [
    "## What happens behind the scenes?\n",
    "\n",
    "When you use `BedrockAgentCoreApp`, it automatically:\n",
    "\n",
    "* Creates an HTTP server that listens on the port 8080\n",
    "* Implements the required `/invocations` endpoint for processing the agent's requirements\n",
    "* Implements the `/ping` endpoint for health checks (very important for asynchronous agents)\n",
    "* Handles proper content types and response formats\n",
    "* Manages error handling according to the AWS standards"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6820ca8f-a8a8-4f34-b4ef-b6dad3776261",
   "metadata": {},
   "source": [
    "## Deploying the agent to AgentCore Runtime\n",
    "\n",
    "The `CreateAgentRuntime` operation supports comprehensive configuration options, letting you specify container images, environment variables and encryption settings. You can also configure protocol settings (HTTP, MCP) and authorization mechanisms to control how your clients communicate with the agent. \n",
    "\n",
    "**Note:** Operations best practice is to package code as container and push to ECR using CI/CD pipelines and IaC\n",
    "\n",
    "In this tutorial can will the Amazon Bedrock AgentCode Python SDK to easily package your artifacts and deploy them to AgentCore runtime."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0861401-a111-4ade-9e02-50f52fdfa9b1",
   "metadata": {},
   "source": [
    "### Creating runtime role\n",
    "\n",
    "Before starting, let's create an IAM role for our AgentCore Runtime. We will do so using the utils function pre-developed for you."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54dd2fdf-985c-4a70-8b87-071783a209de",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "\n",
    "# Get the current notebook's directory\n",
    "current_dir = os.path.dirname(os.path.abspath('__file__' if '__file__' in globals() else '.'))\n",
    "\n",
    "utils_dir = os.path.join(current_dir, '..')\n",
    "utils_dir = os.path.join(utils_dir, '..')\n",
    "utils_dir = os.path.abspath(utils_dir)\n",
    "\n",
    "# Add to sys.path\n",
    "sys.path.insert(0, utils_dir)\n",
    "print(\"sys.path[0]:\", sys.path[0])\n",
    "\n",
    "from utils import create_agentcore_role\n",
    "\n",
    "agent_name=\"strands_agents_openai\"\n",
    "agentcore_iam_role = create_agentcore_role(agent_name=agent_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8855aceb-b79f-4aaa-b16f-8577c059816a",
   "metadata": {},
   "source": [
    "### Configure AgentCore Runtime deployment\n",
    "\n",
    "Next we will use our starter toolkit to configure the AgentCore Runtime deployment with an entrypoint, the execution role we just created and a requirements file. We will also configure the starter kit to auto create the Amazon ECR repository on launch.\n",
    "\n",
    "During the configure step, your docker file will be generated based on your application code\n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/configure.png\" width=\"60%\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e79eba2-ca59-463f-9ebf-56e362d7ae66",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bedrock_agentcore_starter_toolkit import Runtime\n",
    "from boto3.session import Session\n",
    "import boto3\n",
    "import json\n",
    "boto_session = Session()\n",
    "region = boto_session.region_name\n",
    "region\n",
    "\n",
    "agentcore_runtime = Runtime()\n",
    "\n",
    "response = agentcore_runtime.configure(\n",
    "    entrypoint=\"strands_agents_openai.py\",\n",
    "    execution_role=agentcore_iam_role['Role']['Arn'],\n",
    "    auto_create_ecr=True,\n",
    "    requirements_file=\"requirements.txt\",\n",
    "    region=region\n",
    ")\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e1b84cc-798e-472c-ac0b-2c315f4b704d",
   "metadata": {},
   "source": [
    "### Launching agent to AgentCore Runtime\n",
    "\n",
    "Now that we've got a docker file, let's launch the agent to the AgentCore Runtime. This will create the Amazon ECR repository and the AgentCore Runtime\n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/launch.png\" width=\"85%\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17a32ab8-7701-4900-8055-e24364bdf35c",
   "metadata": {},
   "outputs": [],
   "source": [
    "launch_result = agentcore_runtime.launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0ae9c09-09db-4a76-871a-92eacd96b9c3",
   "metadata": {},
   "source": [
    "### Checking for the AgentCore Runtime Status\n",
    "Now that we've deployed the AgentCore Runtime, let's check for it's deployment status"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "afa6ac09-9adb-4846-9fc1-4d12aeb74853",
   "metadata": {},
   "outputs": [],
   "source": [
    "status_response = agentcore_runtime.status()\n",
    "status = status_response.endpoint['status']\n",
    "end_status = ['READY', 'CREATE_FAILED', 'DELETE_FAILED', 'UPDATE_FAILED']\n",
    "while status not in end_status:\n",
    "    time.sleep(10)\n",
    "    status_response = agentcore_runtime.status()\n",
    "    status = status_response.endpoint['status']\n",
    "    print(status)\n",
    "status"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7f89c56-918a-4cab-beaa-c7ac43a2ba29",
   "metadata": {},
   "source": [
    "### Invoking AgentCore Runtime\n",
    "\n",
    "Finally, we can invoke our AgentCore Runtime with a payload\n",
    "\n",
    "<div style=\"text-align:left\">\n",
    "    <img src=\"images/invoke.png\" width=85%\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d909e42-e1a0-407f-84c2-3d16cc889cd3",
   "metadata": {},
   "outputs": [],
   "source": [
    "invoke_response = agentcore_runtime.invoke({\"prompt\": \"Hi, what can you do?\"})\n",
    "invoke_response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fefa09f2-d25a-483f-aedb-11690bb8923a",
   "metadata": {},
   "source": [
    "### Processing invocation results\n",
    "\n",
    "We can now process our invocation results to include it in an application"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11249103-cfb3-47b5-970d-981a977a225a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Markdown, display\n",
    "import json\n",
    "response_text = json.loads(invoke_response['response'][0].decode(\"utf-8\"))\n",
    "display(Markdown(response_text))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c1d2bce-be41-478c-8bed-b4037c385795",
   "metadata": {},
   "source": [
    "### Invoking AgentCore Runtime with boto3\n",
    "\n",
    "Now that your AgentCore Runtime was created you can invoke it with any AWS SDK. For instance, you can use the boto3 `invoke_agent_runtime` method for it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f84e68d-6c04-41b9-bf5b-60edc3fa0985",
   "metadata": {},
   "outputs": [],
   "source": [
    "import boto3\n",
    "agent_arn = launch_result.agent_arn\n",
    "agentcore_client = boto3.client(\n",
    "    'bedrock-agentcore',\n",
    "    region_name=region\n",
    ")\n",
    "\n",
    "boto3_response = agentcore_client.invoke_agent_runtime(\n",
    "    agentRuntimeArn=agent_arn,\n",
    "    qualifier=\"DEFAULT\",\n",
    "    payload=json.dumps({\"prompt\": \"How much is 2X2?\"})\n",
    ")\n",
    "if \"text/event-stream\" in boto3_response.get(\"contentType\", \"\"):\n",
    "    content = []\n",
    "    for line in boto3_response[\"response\"].iter_lines(chunk_size=1):\n",
    "        if line:\n",
    "            line = line.decode(\"utf-8\")\n",
    "            if line.startswith(\"data: \"):\n",
    "                line = line[6:]\n",
    "                logger.info(line)\n",
    "                content.append(line)\n",
    "    display(Markdown(\"\\n\".join(content)))\n",
    "else:\n",
    "    try:\n",
    "        events = []\n",
    "        for event in boto3_response.get(\"response\", []):\n",
    "            events.append(event)\n",
    "    except Exception as e:\n",
    "        events = [f\"Error reading EventStream: {e}\"]\n",
    "    display(Markdown(json.loads(events[0].decode(\"utf-8\"))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d3fdfe404469632",
   "metadata": {},
   "source": [
    "## Cleanup (Optional)\n",
    "\n",
    "Let's now clean up the AgentCore Runtime created"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76a6cf1416830a54",
   "metadata": {},
   "outputs": [],
   "source": [
    "agentcore_control_client = boto3.client(\n",
    "    'bedrock-agentcore-control',\n",
    "    region_name=region\n",
    ")\n",
    "ecr_client = boto3.client(\n",
    "    'ecr',\n",
    "    region_name=region\n",
    "    \n",
    ")\n",
    "\n",
    "iam_client = boto3.client('iam')\n",
    "\n",
    "runtime_delete_response = agentcore_control_client.delete_agent_runtime(\n",
    "    agentRuntimeId=launch_result.agent_id\n",
    ")\n",
    "\n",
    "response = ecr_client.delete_repository(\n",
    "    repositoryName=launch_result.ecr_uri.split('/')[1],\n",
    "    force=True\n",
    ")\n",
    "\n",
    "policies = iam_client.list_role_policies(\n",
    "    RoleName=agentcore_iam_role['Role']['RoleName'],\n",
    "    MaxItems=100\n",
    ")\n",
    "\n",
    "for policy_name in policies['PolicyNames']:\n",
    "    iam_client.delete_role_policy(\n",
    "        RoleName=agentcore_role_name,\n",
    "        PolicyName=policy_name\n",
    "    )\n",
    "iam_response = iam_client.delete_role(\n",
    "    RoleName=agentcore_role_name\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b118ad38-feeb-4d1d-9d57-e5c845becc56",
   "metadata": {},
   "source": [
    "# Congratulations!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
